// Copyright 2022 Huawei Cloud Computing Technology Co., Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import PROTOCOL_CONFIG from './config/protocolConfig';
import Util from './Util';
import {DEVICE_ORIENTATION_ANGLES} from './config/commonConfig';

// 屏幕刷新频率一般为60fps
const MOVE_THROTTLE_DELAY = 16.6;
const ORIENTATION_PORTRAIT = 'PORTRAIT';
const ORIENTATION_REVERSE_PORTRAIT = 'REVERSE_PORTRAIT';
const ORIENTATION_LANDSCAPE = 'LANDSCAPE';
const ORIENTATION_REVERSE_LANDSCAPE = 'REVERSE_LANDSCAPE';
const KEYBOARD_ID_PREFIX = 'phoenix_keyboard_';
const ORIENTATION_DEGRESS = {
    'PORTRAIT': 0,
    'LANDSCAPE': 90,
    'REVERSE_LANDSCAPE': -90,
    'REVERSE_PORTRAIT': 180
};
const ORIENTATION_ORIGIN = {
    'PORTRAIT': 0,
    'LANDSCAPE': 3,
    'REVERSE_LANDSCAPE': 3,
    'REVERSE_PORTRAIT': 2
};
const ORIENTATION_ORIGIN_MOBILE = {
    'PORTRAIT': 0,
    'LANDSCAPE': 3,
    'REVERSE_LANDSCAPE': 1,
    'REVERSE_PORTRAIT': 2
};
const KEYBOARD_MODE = {
    'KEYBOARD_MAP': 'KEYBOARD_MAP',
    'KEYBOARD_INPUT': 'KEYBOARD_INPUT'
};

export default class TouchHandler {
    constructor({player, isMobile, sendHandler, isDebug, autoRotate, inputId = '', containerId}) {
        // 离开画面前最后一次鼠标操作
        this.lastMouseAction = '';
        this.container = document.getElementById(containerId);
        // 监听触控鼠键事件的Dom
        this.displayDom = player;
        // 事件回调，可通过该函数获取模拟事件
        this.sendHandler = sendHandler;
        // 是否移动端
        this.isMobile = isMobile;
        // debug下，显示调测过程信息
        this.isDebug = isDebug;
        this.autoRotate = autoRotate;
        // 远端云手机信息
        this.phone = {...PROTOCOL_CONFIG.DEFAULT_RESOLUTION};
        this.resolution = {...PROTOCOL_CONFIG.DEFAULT_RESOLUTION};
        // 手机浏览器接入，使用浏览器可用宽高
        if (this.isMobile) {
            this.resolution = {
                width: window.innerWidth,
                height: window.innerHeight
            }
        }
        
        // 云手机画面方向，默认竖屏，若启动竖屏，CAE不发送旋转msg
        this.orientation = ORIENTATION_PORTRAIT;
        // player显示内容区坐标相对contain的偏移
        this.displayBox = {
            top: 0,
            left: 0,
            ...PROTOCOL_CONFIG.DEFAULT_RESOLUTION
        };
        // 本地播放画面相对远端手机大小的比例
        this.scale = {
            x: 1,
            y: 1
        };
        this.keyboardMode = KEYBOARD_MODE.KEYBOARD_MAP;
        // 存储模拟数据
        this.simulationState = {
            mouse: {},
            touch: {}
        };
        // 多点触控不超过20个触点
        this.virtualTouchIds = new Uint8Array(20);
        this.mouseCode = {
            primary: 0,
            assist: 1,
            secondary: 2
        };
        // 是否拖拽模式
        this.dragMode = true;
        this.util = new Util();
        this.touchListKey = 'changedTouches';
        // 存储用于定时触发move事件的定时器
        this.moveTimer = {};
        this.viewportOrientation = window.orientation;
        this.isApple = /(iPhone|iPad|iPod|iOS|Macintosh)/i.test(navigator && navigator.userAgent || '');
        this.inputId = inputId;
    }
    
    start() {
        this.updateScale();

        this.resize();
        this.util.bind(window, 'resize', this.resize.bind(this));

        if (this.displayDom) {
            if (this.isMobile) {
                this.util.bind(this.displayDom, 'touchstart', this.onTouchStart.bind(this));
                this.util.bind(this.displayDom, 'touchend', this.onTouchEnd.bind(this));
                this.util.bind(this.displayDom, 'touchmove', this.onTouchMove.bind(this));
                this.util.bind(this.displayDom, 'touchcancel', this.onTouchEnd.bind(this));
            } else {
                this.util.bind(this.displayDom, 'mousedown', this.onMouseDown.bind(this));
                this.util.bind(this.displayDom, 'mouseup', this.onMouseUp.bind(this));
                this.util.bind(this.displayDom, 'mousemove', this.util.throttle(
                    this.onMouseMove.bind(this), MOVE_THROTTLE_DELAY, this).bind(this));
                this.util.bind(this.container, 'mouseleave', this.onMouseLeave.bind(this));
                
                this.util.bind(this.displayDom, 'contextmenu', () => false);
            }
    
            this.displayDom.focus();

            this.autoRotate && this.util.bind(window, 'orientationchange', this.viewportOrientationChange.bind(this));
        }
    }

    viewportOrientationChange() {
        this.viewportOrientation = window.orientation;
    }

    /**
     * 更新方向
     * @param {string} orientation 方向
     **/
    updateOrientation(orientation) {
        this.orientation = orientation;
        this.resize();
    }

    updateResolution(width, height) {
        this.resolution = {width, height};
        this.resize();
    }

    updateKeyBoardMode(mode) {
        if (!KEYBOARD_MODE[mode]) {
            return;
        }
        this.keyboardMode = mode;
    }

    /**
     * 获取Android触控事件类型
     * @param {string} type H5触控事件类型
     * @param {object} touchRecords 已触发的触控鼠键记录
     * @returns {string} Android触控事件类型
     */
    getTouchAction(type, touchRecords) {
        const mouseStateNum = touchRecords.mouse && Object.keys(touchRecords.mouse).length || 0;
        const touchNum = touchRecords.touch && Object.keys(touchRecords.touch).length || 0;
        // 方向键合成一个vId，需要去重
        const keyboardNum = [...new Set(touchRecords.keyboard && Object.values(touchRecords.keyboard) || [])].length;
        const total = mouseStateNum + touchNum + keyboardNum;
        let prefix = '';
        if ((type === 'UP' && total) || (type === 'DOWN' && total > 1)) {
            prefix = 'POINTER_';
        }
    
        return prefix + type;
    }
    
    // 阻止原生事件
    preventDefault(event) {
        event.stopPropagation();
        event.preventDefault();
    }

    onTouchStart(event) {
        event = event || window.event;
        this.preventDefault(event);

        // TouchList是array like结构的对象，包含length属性。
        const touchList = event[this.touchListKey] || {length: 0};
        for (let len = touchList.length, index = 0; index < len; index++) {
            const touch = touchList.item(index) || {};
            const existTouch = this.simulationState.touch[touch.identifier];
            if (existTouch) {
                this.sendTouchMsg(existTouch, 'UP');
                this.freeVirtualTouchId(existTouch.vId);
                delete this.simulationState.touch[touch.identifier];
            }
    
            touch.vId = this.allocVirtualTouchId();
            this.simulationState.touch[touch.identifier] = touch;
            const pos = this.getCurrentMousePos(touch);
            this.sendTouchMsg({
                ...pos,
                vId: touch.vId
            }, this.getTouchAction('DOWN', this.simulationState));
        }
    }
    
    onTouchMove(event) {
        event = event || window.event;
        this.preventDefault(event);
        // TouchList是array like结构的对象，包含length属性。
        const touchList = event[this.touchListKey] || {length: 0};
        for (let len = touchList.length, index = 0; index < len; index++) {
            const touch = touchList.item(index) || {};
            const existTouch = this.simulationState.touch[touch.identifier];
            const pos = this.getCurrentMousePos(touch);
            this.sendTouchMsg({
                ...pos,
                vId: existTouch && existTouch.vId || 0
            }, 'MOVE');
            if((pos.x > this.displayBox.width || pos.x < 0 || pos.y < 0 || pos.y > this.displayBox.height)){
                this.clearTouchAndMouse()
            }
        }
    }

    onTouchEnd(event) {
        event = event || window.event;
        this.preventDefault(event);
        // TouchList是array like结构的对象，包含length属性。
        const touchList = event[this.touchListKey] || {length: 0};
        for (let len = touchList.length, index = 0; index < len; index++) {
            const touch = touchList.item(index) || {};
            const existTouch = this.simulationState.touch[touch.identifier];
            if (existTouch) {
                this.freeVirtualTouchId(existTouch.vId);
                delete this.simulationState.touch[touch.identifier];
            }
    
            const pos = this.getCurrentMousePos(touch);
            this.sendTouchMsg({
                ...pos,
                vId: existTouch && existTouch.vId || 0
            }, this.getTouchAction('UP', this.simulationState));
        }
    }
    
    _mouseAction(event, action) {
        this.lastMouseAction = action;
        const mouseCode = event.button;
        const pos = this.getCurrentMousePos(event);
        let mouseState = this.simulationState.mouse;
        let vId = mouseState[mouseCode];
        if (action === 'UP') {
            this.freeVirtualTouchId(vId);
            delete mouseState[mouseCode];
        } else {
            vId = this.allocVirtualTouchId();
            mouseState[mouseCode] = vId;
        }
        this.sendTouchMsg({
            ...pos,
            vId
        }, this.getTouchAction(action, this.simulationState));
    }
    
    onMouseDown(event) {
        this.container.focus()
        event = event || window.event;
        this.preventDefault(event);
        if (event.button === this.mouseCode.assist) {
            this.dragMode = !this.dragMode;
            if (!this.dragMode) {
                this._mouseAction(event, 'DOWN');
            } else {
                this._mouseAction(event, 'UP');
            }
        } else {
            this._mouseAction(event, 'DOWN');
        }
    }
    
    onMouseMove(event) {
        event = event || window.event;
        this.preventDefault(event);
        const mouseCodes = Object.keys(this.simulationState.mouse);
        const mouseCode = mouseCodes.find(code => code in this.simulationState.mouse);
        if (mouseCode !== undefined) {
            let pos = this.getCurrentMousePos(event);
            this.sendTouchMsg({
                ...pos,
                vId: this.simulationState.mouse[mouseCode]
            }, 'MOVE');
        }
    }

    onMouseLeave() {
        if(this.lastMouseAction === 'DOWN'){
            this.lastMouseAction ="UP";           
            this.clearTouchAndMouse()
        }        
    }

    onMouseUp(event) {
        event = event || window.event;
        this.preventDefault(event);
        if (event.button !== this.mouseCode.assist) {
            this._mouseAction(event, 'UP');
        }
    }
    
    isSameOrientation() {
        if (!this.isMobile) {
            return !['LANDSCAPE', ORIENTATION_REVERSE_LANDSCAPE].includes(this.orientation);
        }

        // 若云手机画面横屏，当前认为云手机方向为逆时针旋转90度。
        // 故若不自动旋转，云手机画面竖屏时，浏览器和云手机画面方向一致；
        // 若自动旋转，1）真机方向竖屏，则云手机会旋转至和浏览器一个方向，2）若真机左旋转或又旋转至横屏，则云手机画面会旋转至浏览器逆时针90度的位置
        return ((!this.autoRotate && this.orientation === ORIENTATION_PORTRAIT)
            || (this.autoRotate && (this.viewportOrientation === ORIENTATION_DEGRESS.PORTRAIT || this.viewportOrientation === ORIENTATION_DEGRESS.REVERSE_PORTRAIT)));
    }

    getScale(current, target) {
        if (this.isSameOrientation()) {
            return {
                x: current.width ? (target.width / current.width) : 1,
                y: current.height ? (target.height / current.height) : 1
            };
        }

        return {
            x: current.height ? (target.width / current.height) : 1,
            y: current.width ? (target.height / current.width) : 1
        };
    }
    
    // 更新缩放比例
    updateScale() {
        this.scale = this.getScale(this.displayBox, this.phone);
    }
    
    // 更新player显示区域信息
    updateContainBox(container) {
        const scaleObj = this.getScale(container, this.resolution);
        const scale = Math.max(scaleObj.x, scaleObj.y);
        if (this.isSameOrientation()) {
            this.displayBox.width = this.resolution.width / scale;
            this.displayBox.height = this.resolution.height / scale;
        } else {
            this.displayBox.width = this.resolution.height / scale;
            this.displayBox.height = this.resolution.width / scale;
        }
    
        this.displayBox.left = (container.width - this.displayBox.width) >> 1;
        this.displayBox.top = (container.height - this.displayBox.height) >> 1;
    }
    
    // 界面resize，更新显示区域信息和键盘位置信息，以及缩放比例等和大小位置相关的信息
    resize(width, height) {
        if (!this.displayDom) {
            return;
        }
    
        if (width && height) {
            this.displayBox.width = width;
            this.displayBox.height = height;
        } else {
            // 旋转后，通过getClientRects拿到的宽高为旋转后宽高，所见即所得，如（宽，高）=（200px, 100px）的块旋转90度后获取到（宽，高）=（100px, 200px）
            const displayRects = this.displayDom.getClientRects();
            this.displayBox.width = displayRects && displayRects[0] && displayRects[0].width;
            this.displayBox.height = displayRects && displayRects[0] && displayRects[0].height;
        }

        this.displayBox.top = 0;
        this.displayBox.left = 0;
        // 若player等比例填充，则实际内容区的宽高并非获取到的宽高
        if (this.displayDom.style.objectFit === 'contain') {
            this.updateContainBox({...this.displayBox});
        }
    
        this.updateScale();
    }
    
    /**
     * 计算触控点相对于云手机画面左上角的位置信息
     * @param {object} eventTouch 触控事件
     * @return {object} 位置
     */
    getCurrentMousePos(eventTouch) {
        const docRects = document.documentElement.getClientRects()[0];
        const displayRects = this.displayDom.getClientRects()[0];

        let x = eventTouch.pageX - displayRects.left - this.displayBox.left;
        let y = eventTouch.pageY - displayRects.top - this.displayBox.top + docRects.top;
        if (this.orientation !== ORIENTATION_REVERSE_LANDSCAPE) {
            return {
                x: x,
                y: y
            };
        } else {
            return {
                x: this.isMobile ? x : this.displayBox.width - x,
                y: this.isMobile ? y : this.displayBox.height - y
            };
        }
    }
    
    // 分配虚拟ID
    allocVirtualTouchId() {
        const index = this.virtualTouchIds.indexOf(0);
        if (index === -1) {
            this.virtualTouchIds.map(() => 0);
            this.virtualTouchIds[0] = 1;
            return 0;
        }
    
        this.virtualTouchIds[index] = 1;
        return index;
    }
    
    // 释放虚拟ID
    freeVirtualTouchId(virtualId) {
        this.virtualTouchIds[virtualId] = 0;
    }
    
    getMsgBuf(data) {
        if (!this.sendHandler) {
            return;
        }
    
        const PACKAGE_HEADER_LENGTH = 8;
        const TOUCH_MSG_BODY_LENGTH = 17;
        const TOUCH_ACTION = 6;

        let buf = new Uint8Array(PACKAGE_HEADER_LENGTH + TOUCH_MSG_BODY_LENGTH);
        buf[0] = 90;
        buf[1] = 90;
        buf[2] = 180 + TOUCH_ACTION;
        buf[3] = TOUCH_ACTION;
        buf[7] = TOUCH_MSG_BODY_LENGTH;
        buf[8] = data.id;
        buf[9] = data.action;
        buf[10] = (data.x >> 8) & 0xFF;
        buf[11] = data.x & 0xFF;
        buf[12] = (data.y >> 8) & 0xFF;
        buf[13] = data.y & 0xFF;
        buf[14] = (data.pressure >> 8) & 0xFF;
        buf[15] = data.pressure & 0xFF;
        buf[16] = (data.time & 0xFF000000) >> 24;
        buf[17] = (data.time & 0x00FF0000) >> 16;
        buf[18] = (data.time & 0x0000FF00) >> 8;
        buf[19] = data.time & 0x000000FF;
        buf[20] = data.orientation;
        buf[21] = (data.height >> 8) & 0xFF;
        buf[22] = data.height & 0xFF;
        buf[23] = (data.width >> 8) & 0xFF;
        buf[24] = data.width & 0xFF;
        return buf;
    }

    getKeyCodeEventMsgBuf(data) {
        if (!this.sendHandler) {
            return;
        }
    
        const PACKAGE_HEADER_LENGTH = 8;
        const TOUCH_MSG_BODY_LENGTH = 4;
        const TOUCH_ACTION = 15;

        let buf = new Uint8Array(PACKAGE_HEADER_LENGTH + TOUCH_MSG_BODY_LENGTH);
        buf[0] = 90;
        buf[1] = 90;
        buf[2] = 180 + TOUCH_ACTION;
        buf[3] = TOUCH_ACTION;
        buf[7] = TOUCH_MSG_BODY_LENGTH;
        buf[8] = (data.keycode >> 8) & 0xFF;
        buf[9] = data.keycode & 0xFF;
        buf[10] = (data.action >> 8) & 0xFF;
        buf[11] = data.action & 0xFF;
        return buf;
    }

    /**
     * 通过回调，发送模拟的触控事件
     * touchstart : 触摸开始（手指放在触摸屏上）
     * touchmove : 拖动（手指在触摸屏上移动）
     * touchend : 触摸结束（手指从触摸屏上移开）
     * touchenter ：移动的手指进入一个dom元素。
     * touchleave ：移动的手指离开一个dom元素。
     */
    sendTouchMsg(touch, action) {
        // 触控点不在player区域，不发送指令
        if (touch.x > this.displayBox.width || touch.x < 0 || touch.y < 0 || touch.y > this.displayBox.height) {
            return;
        }
        let finalOrientation;
        if (this.isMobile) {
            finalOrientation = ORIENTATION_ORIGIN_MOBILE[this.orientation];
        } else {
            finalOrientation = ORIENTATION_ORIGIN[this.orientation];
        }
        const msg = {
            ...touch,
            action: PROTOCOL_CONFIG.ACTIONS_TYPE[action],
            id: touch.vId,
            pressure: 129,
            time: -1,
            orientation: finalOrientation,
            height: this.displayBox.height,
            width: this.displayBox.width
        };
        const msgBuf = this.getMsgBuf(msg);
        this.sendHandler(msgBuf);
    }

    sendKeyCodeEventMsg(action, keycode) {
        const msg = {
            keycode,
            action
        };
        const msgBuf = this.getKeyCodeEventMsgBuf(msg);
        this.sendHandler(msgBuf);
    }

    clearTouchAndMouse() {
        this.freeVirtualTouchId(0);
        this.simulationState.touch = {};
        this.simulationState.mouse = {};
        this.sendTouchMsg(
            {
                x: 0,
                y: 0,
                vId: 0,
            },
            "UP"
        );
    }
    
    destroy() {
        this.util.unbind(window, 'resize');
        this.util.unbind(window, 'keydown');
        this.util.unbind(window, 'keyup');
        this.util.unbind(this.displayDom);
        this.autoRotate && this.util.unbind(window, 'orientationchange');
        Object.keys(this.moveTimer).forEach(key => {
            this.moveTimer[key] && clearInterval(this.moveTimer[key]);
        });

        // 删除增加的keyboard Dom
        if (this.displayDom && this.displayDom.parentNode) {
            const childrens = this.displayDom.parentNode.children || [];
            const keys = this.displayKeyboardMap && Object.keys(this.displayKeyboardMap) || [];
            let ids = keys.map(key => `${KEYBOARD_ID_PREFIX}${key}`);
            ids.push('posTracker');
            let removeList = [];
            for (let i = 0, len = childrens.length; i < len; i++) {
                if (ids.indexOf(childrens[i].id) > -1) {
                    removeList.push(childrens[i]);
                }
            }

            removeList.forEach(item => this.displayDom.parentNode.removeChild(item));
        }

        this.displayDom = null;
    }
}

